import { isSameVnode } from './index';

function createComponent(vnode) {
  let i = vnode.data;
  if ((i = i.hook) && (i = i.init)) {
    i(vnode);
  }
  if (vnode.componentInstance) {
    return true;
  }
}

export function createElm(vnode) {
  if (typeof vnode !== 'object') {
    return document.createTextNode(vnode);
  }
  let { tag, data, children, text } = vnode;
  if (typeof tag === 'string') {
    if (createComponent(vnode)) {
      return vnode.componentInstance.$el;
    }
    vnode.el = document.createElement(tag);
    patchProps(vnode.el, {}, data);
    children.forEach((child) => {
      vnode.el.appendChild(createElm(child));
    });
  } else {
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}

export function patchProps(el, oldProps, props) {
  // 处理 style
  let oldStyles = oldProps?.style ?? {};
  let newStyle = props?.style ?? {};
  for (let key in oldStyles) {
    // 旧节点有的 style，新的没有，删掉该 style
    if (!newStyle[key]) {
      el.style[key] = '';
    }
  }

  for (let key in oldProps) {
    // 旧节点有的 attr，新的没有，删掉该 attr
    if (!props[key]) {
      el.removeAttribute(key);
    }
  }

  // 添加属性
  for (let key in props) {
    if (key === 'style') {
      for (let styleName in props.style) {
        el.style[styleName] = props.style[styleName];
      }
    } else {
      el.setAttribute(key, props[key]);
    }
  }
}

export function patch(oldVnode, vnode) {
  if (!oldVnode) {
    // 组件
    return createElm(vnode);
  }
  // oldVnode 初始化时是 el 真实 DOM，更新时是虚拟 DOM
  const isRealElement = oldVnode.nodeType;
  if (isRealElement) {
    // 首次加载，根据 vnode 创建真实 DOM （createElm 函数实现）,把原来的 el 节点删掉并把生成的 DOM 插入。
    const elm = oldVnode;
    const parentElm = elm.parentNode;
    const newElm = createElm(vnode);

    parentElm.insertBefore(newElm, elm.nextSibling); // 父节点插入生成的 DOM ，位置处于原来 DOm 的 next。
    parentElm.removeChild(elm);

    return newElm;
  } else {
    // 更新视图 diff

    return patchVnode(oldVnode, vnode);
  }
}

function patchVnode(oldVnode, vnode) {
  if (!isSameVnode(oldVnode, vnode)) {
    // 新旧节点的 tag 不同，key 也不同。用新节点创建一份 dom，替换掉旧节点的 dom。
    let el = createElm(vnode);
    oldVnode.el.parentNode.replaceChild(el, oldVnode.el);
    return el;
  }

  let el = (vnode.el = oldVnode.el);
  if (oldVnode.tag === undefined) {
    // 处理文本节点，经过上面的 if 说明新旧节点都是文本节点
    if (oldVnode.text !== vnode.text) {
      el.textContent = vnode.text;
    }
  }

  // 处理标签节点
  // 1. 属性
  patchProps(el, oldVnode.data, vnode.data);

  // 2.子节点
  let oldChildren = oldVnode.children || [];
  let newChildren = vnode.children || [];

  if (oldChildren.length > 0 && newChildren.length > 0) {
    // 新旧节点都有子节点
    updateChildren(el, oldChildren, newChildren);
  } else if (newChildren.length > 0) {
    // 新有子节点，旧无,遍历新节点插入 el。
    mountChildren(el, newChildren);
  } else if (oldChildren.length > 0) {
    el.innerHTML = '';
  }
  return el;
}

function mountChildren(el, newChildren) {
  newChildren.forEach((child) => {
    el.appendChild(createElm(child));
  });
}

function updateChildren(el, oldChildren, newChildren) {
  let oldStartIndex = 0;
  let newStartIndex = 0;
  let oldEndIndex = oldChildren.length - 1;
  let newEndIndex = newChildren.length - 1;

  let oldStartVnode = oldChildren[0];
  let newStartVnode = newChildren[0];
  let oldEndVnode = oldChildren[oldEndIndex];
  let newEndVnode = newChildren[newEndIndex];

  function makeIndexByKey(children) {
    let map = {};
    children.forEach((child, index) => {
      map[child.key] = index;
    });
    return map;
  }
  let map = makeIndexByKey(oldChildren);

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (!oldStartVnode) {
      oldStartVnode = oldChildren[++oldStartIndex];
    } else if (!oldEndVnode) {
      oldEndVnode = oldChildren[--oldEndIndex];
    } else if (isSameVnode(oldStartVnode, newStartVnode)) {
      //头头 同一节点  第一步递归  第二步后移新旧头指针
      patchVnode(oldStartVnode, newStartVnode);
      oldStartVnode = oldChildren[++oldStartIndex];
      newStartVnode = newChildren[++newStartIndex];
    } else if (isSameVnode(oldEndVnode, newEndVnode)) {
      //尾尾 同一节点  第一步递归  第二步前移新旧尾指针
      patchVnode(oldEndVnode, newEndVnode);
      oldEndVnode = oldChildren[--oldEndIndex];
      newEndVnode = newChildren[--newEndIndex];
    } else if (isSameVnode(oldEndVnode, newStartVnode)) {
      // 尾头 同一节点  第一步递归  第二步在旧头节点之前创建一个旧尾节点 第三步旧尾指针前移，新头指针后移
      patchVnode(oldEndVnode, newStartVnode);
      el.insertBefore(oldEndVnode.el, oldStartVnode.el);
      oldEndVnode = oldChildren[--oldEndIndex];
      newStartVnode = newChildren[++newStartIndex];
    } else if (isSameVnode(oldStartVnode, newEndVnode)) {
      // 头尾 同一节点  第一步递归  第二步在旧尾节点的下一个节点之前创建旧头节点  第三步新尾指针前移，旧头指针后移
      patchVnode(oldStartVnode, newEndVnode);
      el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);
      oldStartVnode = oldChildren[++oldStartIndex];
      newEndVnode = newChildren[--newEndIndex];
    } else {
      // 乱序  新头节点两种情况：1.要不是旧头尾指针之间的节点 2.要不就是全新节点
      let moveIndex = map[newStartVnode.key]; //从旧节点字典里找 key 对应的 nodeIndex
      if (moveIndex !== undefined) {  // 能从字典找到，当前代表是情况1
        let moveVnode = oldChildren[moveIndex];  // 从旧节点中找出要操作的节点
        el.insertBefore(moveVnode.el, oldStartVnode.el); // 移动到旧头节点之前
        oldChildren[moveIndex] = undefined; // 销毁旧节点中原位置的该节点
        patchVnode(moveVnode, newStartVnode); // 递归当前的新旧头结点
      } else {// 情况2
        el.insertBefore(createElm(newStartVnode), oldStartVnode.el); // 在旧头节点之前创建一个新头节点
      }

      newStartVnode = newChildren[++newStartIndex]; // 新头指针后移
    }
  }

  if (newStartIndex <= newEndIndex) { // 循环结束时 如果新头尾指针之间还有节点  把这些节点插入到 newEndIndex+1 号节点之前。
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      let childEl = createElm(newChildren[i]);
      // el.appendChild(childEl);
      let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null;
      el.insertBefore(childEl, anchor);
    }
  }

  if (oldStartIndex <= oldEndIndex) {// 循环结束时 如果旧头尾指针之间还有节点  把这些节点从旧节点中删除。
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      if (oldChildren[i]) {
        let childEl = oldChildren[i].el;
        el.removeChild(childEl);
      }
    }
  }
}
